# -*- coding: utf-8 -*-

X = 1
O = -1
EMPTY = 0
MIN_INF = -2
WIN = 1
LOSS = -1

start_state = [0, 0, 0,
               0, 0, 0,
               0, 0, 0]

start_moves = [0, 1, 2, 3, 4, 5, 6, 7, 8]

ai_side = X

def minimax_ai(turn, avail_moves, state):
    # this is the top level of search
    # we search all possible moves
    # (PASS and ROLL in case of the Pig game)
    # and pick the one that returns the highest minimax estimate
    best_move = None
    best_value = -2
    for move in avail_moves:
        new_state = state.copy()
        new_state[move] = turn # X, O
        new_avail_moves = avail_moves.copy()
        new_avail_moves.remove(move)
        v = minimax(-turn, new_state, new_avail_moves, 4)
        #print(move, v)
        if v > best_value:
            best_value = v
            best_move = move
    print(best_value)
    return best_move

def find_win_loss(state):
    # check rows
    for row in range(3):
        roff = row*3
        if state[roff] == state[roff+1] == state[roff+2]:
            if state[roff] == X:
                return WIN
            elif state[roff] == O:
                return LOSS

    # check cols
    for col in range(3):
        if state[col] == state[col+3] == state[col+6]:
            if state[col] == X:
                return WIN
            elif state[col] == O:
                return LOSS
    
    # check diags
    if (state[0] == state[4] == state[8] or
         state[2] == state[4] == state[6]):
        if state[4] == X:
            return WIN
        elif state[4] == O:
            return LOSS

    return 0  # nothing found, NOT draw
    
def minimax(turn, state, avail_moves, depth):
    #print(turn, state, avail_moves, depth)
    # update remaining depth as we go deeper in the search tree
    depth = depth - 1

    # case 1a: somebody won, stop searching
    # return a high value if AI wins, low if it loses.
    win_loss = find_win_loss(state)
    if win_loss != 0:
        return win_loss
    
    # end of search tree
    if not avail_moves:
        return 0

    # case 1b: out of depth, stop searching
    # return game state eval (should be between win and loss)
    if depth < 1:
        return 0

    # case 2: AI's turn (and NOT a chance node):
    # return max value of possible moves (recursively)
    if turn == ai_side:
        best_value = -2
        for move in avail_moves:
            new_state = state.copy()
            new_state[move] = turn # X, O
            new_avail_moves = avail_moves.copy()
            new_avail_moves.remove(move)
            v = minimax(-turn, new_state, new_avail_moves, depth)
            if v > best_value:
                best_value = v
        return best_value
    

    # case 3: player's turn:
    # return min value (assume optimal action from player)
    else:  # turn != ai_side
        best_value = 2
        for move in avail_moves:
            new_state = state.copy()
            new_state[move] = turn # X, O
            new_avail_moves = avail_moves.copy()
            new_avail_moves.remove(move)
            v = minimax(-turn, new_state, new_avail_moves, depth)
            if v < best_value:
                best_value = v
        return best_value

test_state = [0, O, 0,
              0, X, 0,
              0, 0, 0]

best_move = minimax_ai(X, [0, 2, 3, 5, 6, 7, 8], test_state)
print(best_move)
